package com.ikingtech.platform.service.datasource.controller;

import com.ikingtech.framework.sdk.context.event.SystemInitEvent;
import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.datasource.api.DatasourceApi;
import com.ikingtech.framework.sdk.datasource.model.*;
import com.ikingtech.framework.sdk.datasource.model.enums.DatasourceSchemaTypeEnum;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.datasource.entity.DatasourceDO;
import com.ikingtech.platform.service.datasource.exception.DatasourceExceptionInfo;
import com.ikingtech.platform.service.datasource.service.DatasourceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.event.EventListener;

import java.util.List;
import java.util.Map;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/datasource", name = "系统管理-数据源管理", description = "系统管理-数据源管理")
public class DatasourceController implements DatasourceApi {

    private final DatasourceService service;

    private final DataSourceProperties dataSourceProperties;

    @Override
    public R<String> add(DatasourceDTO datasource) {
        String tenantCode = Me.tenantCode();
        if (this.service.nameExist(datasource.getName(), tenantCode)) {
            throw new FrameworkException(DatasourceExceptionInfo.DUPLICATE_NAME);
        }
        DatasourceDO entity = Tools.Bean.copy(datasource, DatasourceDO.class);
        entity.setId(Tools.Id.uuid());
        entity.setTenantCode(tenantCode);
        this.service.save(entity);
        return R.ok(entity.getId());
    }

    @Override
    public R<Object> delete(String id) {
        this.service.removeById(id);
        return R.ok();
    }

    @Override
    public R<Object> update(DatasourceDTO datasource) {
        if (!this.service.exist(datasource.getId())) {
            throw new FrameworkException(DatasourceExceptionInfo.DATASOURCE_NOT_FOUND);
        }
        if (this.service.nameExist(datasource.getId(), datasource.getName(), Me.tenantCode())) {
            throw new FrameworkException(DatasourceExceptionInfo.DUPLICATE_NAME);
        }
        DatasourceDO entity = Tools.Bean.copy(datasource, DatasourceDO.class);
        this.service.updateById(entity);
        return R.ok();
    }

    @Override
    public R<List<DatasourceDTO>> page(DatasourceQueryParamDTO queryParam) {
        return R.ok(this.service.listPage(queryParam).convert(this::modelConvert));
    }

    @Override
    public R<List<DatasourceDTO>> all() {
        return R.ok(this.modelConvert(this.service.listByTenantCode(Me.tenantCode())));
    }

    @Override
    public R<DatasourceDTO> detail(String id) {
        DatasourceDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(DatasourceExceptionInfo.DATASOURCE_NOT_FOUND);
        }
        return R.ok(this.modelConvert(entity));
    }

    @Override
    public R<List<String>> listSchema(DatasourceDTO datasource) {
        List<Map<String, Object>> schemas = this.service.execute(datasource.getType(), datasource.getType().jdbcUrl(datasource.getIp(), datasource.getPort()), datasource.getUsername(), datasource.getPassword(), "select * from information_schema.schemata");
        return R.ok(Tools.Coll.convertList(schemas, schema -> String.valueOf(schema.get("SCHEMA_NAME"))));
    }

    @Override
    public R<List<DatasourceTableDTO>> listTableById(String id) {
        DatasourceDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(DatasourceExceptionInfo.DATASOURCE_NOT_FOUND);
        }
        List<Map<String, Object>> schemaTables = this.service.execute(entity, Tools.Str.format("select * from information_schema.tables where table_schema = '{}'", entity.getSchemaName()));
        List<Map<String, Object>> schemaTableFields = this.service.execute(entity, Tools.Str.format("select * from information_schema.columns where table_schema = '{}'", entity.getSchemaName()));

        return R.ok(Tools.Coll.convertList(schemaTables, schemaTable -> {
            DatasourceTableDTO table = new DatasourceTableDTO();
            String tableName = String.valueOf(schemaTable.get("TABLE_NAME"));
            table.setName(tableName);
            table.setDescription(String.valueOf(schemaTable.get("TABLE_COMMENT")));
            table.setFields(Tools.Coll.convertList(schemaTableFields, schemaTableField -> tableName.equals(String.valueOf(schemaTableField.get("TABLE_NAME"))), schemaTableField -> {
                DatasourceTableFieldDTO field = new DatasourceTableFieldDTO();
                field.setName(String.valueOf(schemaTableField.get("COLUMN_NAME")));
                field.setType(String.valueOf(schemaTableField.get("DATA_TYPE")));
                Long fieldLength = (Long) schemaTableField.get("CHARACTER_MAXIMUM_LENGTH");
                field.setLength(null == fieldLength ? null : fieldLength.intValue());
                field.setNullable("YES".equals(String.valueOf(schemaTableField.get("IS_NULLABLE"))));
                field.setPrimaryKey("PRI".equals(String.valueOf(schemaTableField.get("COLUMN_KEY"))));
                field.setComment(String.valueOf(schemaTableField.get("COLUMN_COMMENT")));
                return field;
            }));
            return table;
        }));
    }

    @EventListener(SystemInitEvent.class)
    public void systemInitEventListener() {
        DatasourceDO entity = this.service.getDefaultDatasource();
        if (null == entity) {
            entity = new DatasourceDO();
            entity.setId(Tools.Id.uuid());
        }
        entity.setName("默认数据源");
        entity.setUsername(this.dataSourceProperties.getUsername());
        entity.setPassword(this.dataSourceProperties.getPassword());
        entity.setType(DatasourceSchemaTypeEnum.valueOfDriverClassName(this.dataSourceProperties.getDriverClassName()).name());
        String host = DatasourceSchemaTypeEnum.parseHostByJdbcUrl(entity.getType(), this.dataSourceProperties.getUrl());
        entity.setIp(Tools.Str.split(host, ":").get(0));
        entity.setPort(Tools.Str.split(host, ":").get(1));
        entity.setJdbcUrl(this.dataSourceProperties.getUrl());
        entity.setType(DatasourceSchemaTypeEnum.MYSQL.name());
        entity.setDefaultDatasource(true);
        entity.setSchemaName("iking_framework");
        this.service.saveOrUpdate(entity);
    }

    private List<DatasourceDTO> modelConvert(List<DatasourceDO> entities) {
        return Tools.Coll.convertList(entities, this::modelConvert);
    }

    private DatasourceDTO modelConvert(DatasourceDO entity) {
        DatasourceDTO datasource = Tools.Bean.copy(entity, DatasourceDTO.class);
        if (null != datasource.getType()) {
            datasource.setTypeName(datasource.getType().description);
        }
        return datasource;
    }

    @EventListener
    public void deleteDatasourceEventListener(TenantDeleteEvent event) {
        this.service.removeByTenantCode(event.getCode());
    }
}
